package com.info.zhiduoduo.pay.interceptor;

import com.alibaba.fastjson.JSON;
import com.info.zhiduoduo.common.utils.LogUtil;
import com.info.zhiduoduo.common.utils.ReflectionUtil;

import com.info.zhiduoduo.common.utils.StringUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.NamedThreadLocal;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

/**
 Created by TerryJ on 2016就/03/14.
 */
@Aspect
@Component
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class RequestBodyAspect {

	//线程的时间
	private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("watch_startTime");

	@Pointcut("execution(public * com.info.zhiduoduo.pay.controller..*(..))")
	public void excudeService() {
	}

	/**
	 * 前置通知，方法调用前被调用
	 *
	 * @param pjp
	 */
	@Before("excudeService()")
	public void doBefore(JoinPoint pjp) throws Throwable {
		long beginTime = System.currentTimeMillis();
		startTimeThreadLocal.set(beginTime);
		Object[] args = pjp.getArgs();
		MethodSignature signature = (MethodSignature) pjp.getSignature();
		Method method = signature.getMethod();
		String methodName = signature.getName();
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes sra = (ServletRequestAttributes) ra;
		HttpServletRequest request = sra.getRequest();
		String url = request.getRequestURI();//URL

		String reqData = "";
		Parameter reqBodyParam = ReflectionUtil.getRequestBody(method);
		if (reqBodyParam != null) {
			for (Object arg : args) {
				if (arg == null) continue;

				//不打印Base64上传图片的二进制字符串
				if (url.startsWith("/img/upload")) continue;

				Class argClazz = arg.getClass();
				//如果请求参数定义的是基本类型的参数，将arg转换成基本类型
				if (reqBodyParam.getType().isPrimitive()) {
					argClazz = ReflectionUtil.transferToPrime(argClazz);
				}

				if (argClazz.equals(reqBodyParam.getType())) {
					//找到请求参数reqestData
					reqData = JSON.toJSONString(arg);
				}
			}
		}

		LogUtil.info("ACTION START====================【Method : " + methodName + "】【RequestURL : " + url + "】====================");
		Map<String, String[]> parameterMap = request.getParameterMap();
		if (parameterMap != null && !parameterMap.isEmpty()) {
			LogUtil.info("url参数或表单参数=" + JSON.toJSONString(parameterMap));
		}
		if (StringUtil.isNotEmpty(reqData)) {
			LogUtil.info("RequestBody参数=" + reqData);
		}
	}

	/**
	 * 后置返回通知
	 * 这里需要注意的是:
	 * 如果参数中的第一个参数为JoinPoint，则第二个参数为返回值的信息
	 * 如果参数中的第一个参数不为JoinPoint，则第一个参数为returning中对应的参数
	 * returning 限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知，否则不执行，对于returning对应的通知方法参数为Object类型将匹配任何目标返回值
	 *
	 * @param jp
	 * @param result
	 */
	@AfterReturning(pointcut = "excudeService()", returning = "result")
	public void doAfterReturning(JoinPoint jp, Object result) {
		try {
			// 处理完请求，返回内容
			long beginTime = startTimeThreadLocal.get();
			long requestTime = System.currentTimeMillis() - beginTime;

			RequestAttributes ra = RequestContextHolder.getRequestAttributes();
			ServletRequestAttributes sra = (ServletRequestAttributes) ra;
			HttpServletRequest request = sra.getRequest();
			String url = request.getRequestURI();//URL
			String methodName = jp.getSignature().getName();

			LogUtil.info("返回result=" + JSON.toJSONString(result));
			LogUtil.info("消耗的时间(毫秒):" + requestTime);
			LogUtil.info("ACTION END====================【Method : " + methodName + "】【RequestURL : " + url + "】====================");

		} catch (Exception e) {
			LogUtil.error("***操作请求日志记录失败doAfterReturning()***" + e);
		}

	}

	/**
	 * 后置异常通知
	 * 定义一个名字，该名字用于匹配通知实现方法的一个参数名，当目标方法抛出异常返回后，将把目标方法抛出的异常传给通知方法；
	 * throwing 限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
	 * 对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
	 *
	 * @param joinPoint
	 * @param exception
	 */
	@AfterThrowing(value = "excudeService()", throwing = "exception")
	public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception) {
		//目标方法名：
		System.out.println(joinPoint.getSignature().getName());
		if (exception instanceof NullPointerException) {
			System.out.println("发生了空指针异常!!!!!");
		}
	}
}
